/**
 * Copyright 2010 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package saig.engine.resources;

import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import saig.engine.models.Tile;


/**
 * Loads all the tile resources.
 * @author Adam
 * @version $Id$
 * 
 * Created on Aug 9, 2010 at 10:36:26 PM 
 */
public final class TileLoader {
	/**
	 * Logger.
	 */
	private final Logger logger = LoggerFactory.getLogger(TileLoader.class);
	/**
	 * The instance of this tile loader.
	 */
	private static final TileLoader INSTANCE = new TileLoader();
	/**
	 * The tile reference file.  The file containing the tile information.
	 */
	public static final String TILE_REFERENCE = "tiles.xml";
	/**
	 * Returns an instance of the tile loader.
	 * @return An instance of the tile loader.
	 */
	public static TileLoader getInstance() {
		return INSTANCE;
	}
	/**
	 * A Map of the available tiles by their id.
	 */
	private final Map<Integer, Tile> map = new HashMap<Integer, Tile>();
	/**
	 * The tile height.
	 */
	private int tileHeight;
	/**
	 * List of available tiles.
	 */
	private final List<Tile> tiles = new ArrayList<Tile>();
	/**
	 * The tile width.
	 */
	private int tileWidth;
	/**
	 * Private constructor.
	 */
	private TileLoader() {
		loadTiles(TILE_REFERENCE);
	}
	/**
	 * Returns a tile by it's id.
	 * @param id The tile id.
	 * @return A Tile or null.
	 */
	public Tile getById(final Integer id) {
		return map.get(id);
	}
	/**
	 * Returns tileHeight.
	 * @return the tileHeight.
	 */
	public int getTileHeight() {
		return tileHeight;
	}
	/**
	 * Returns the list of tiles.
	 * @return The list of tiles.
	 */
	public List<Tile> getTiles() {
		return tiles;
	}
	/**
	 * Returns tileWidth.
	 * @return the tileWidth.
	 */
	public int getTileWidth() {
		return tileWidth;
	}
	/**
	 * Loads a List of tiles by their reference.
	 * Calling this method will change the state of tiles
	 * @param ref The reference value (file name) to load.
	 */
	public synchronized List<Tile> loadTiles(final String ref) {
		tiles.clear(); // Clear the tiles array.
		// Create a temporary map of tile sheet ids to images.
		InputStream configStream = null;
		try {
			logger.debug("Loading tiles from {}.", ref);
			URL url = this.getClass().getClassLoader().getResource(ref);
			if (url != null) {
				configStream = url.openStream();
			} else {
				logger.error("Tile file not found with reference ID [{}]", ref);
				return tiles;
			}
		} catch (IOException e) {
			logger.error("IOException occurred loading tiles.", e);
			return tiles;
		}
		final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {
			final DocumentBuilder db = dbf.newDocumentBuilder();
			final Document doc = db.parse(configStream);
			doc.getDocumentElement().normalize();
			final Element tilesNode = (Element) doc.getElementsByTagName("tiles").item(0);
			tileWidth = Integer.valueOf(tilesNode.getAttribute("width"));
			tileHeight = Integer.valueOf(tilesNode.getAttribute("height"));
			final Image[] tilesheet = ImageLoader.getInstance().getImages(
					tilesNode.getAttribute("image"), tileWidth, tileHeight);
			final NodeList nodeLst = tilesNode.getElementsByTagName("tile");
			Element el = null;
			for (int i = 0; i < nodeLst.getLength(); i++) {
				final Node node = nodeLst.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					el = (Element) node;
					final Tile tile = new Tile();
					tile.setId(Integer.valueOf(el.getAttribute("id")));
					tile.setType(el.getAttribute("type"));
					final String vTranId = el.getAttribute("transitionId");
					if (vTranId != null && vTranId.length() > 0) {
						tile.setTransitionId(Integer.valueOf(vTranId));
					}
					// Get the images from the tile sheet for the tile.
					final int numberOfChildren = node.getChildNodes().getLength();
					final List<Image> tileImages = new ArrayList<Image>();
					for (int ccnt = 0; ccnt < numberOfChildren; ccnt++) {
						Node imageNode = node.getChildNodes().item(ccnt);
						if (imageNode.getNodeType() == Node.ELEMENT_NODE) {
							int loc = Integer.valueOf(((Element) imageNode).getAttribute("loc"));
							tileImages.add(tilesheet[loc]);
						}
					}
					tile.setImages(tileImages.toArray(new Image[tileImages.size()]));
					tiles.add(tile);
					map.put(tile.getId(), tile);
				}
			}
		} catch (final ParserConfigurationException parserEx) {
			logger.error("Error parsing tile file", parserEx);
		} catch (final SAXException saxEx) {
			logger.error("Error parsing tile file", saxEx);
		} catch (final IOException ioEx) {
			logger.error("Error parsing tile file", ioEx);
		}
		logger.debug("Loaded {} tiles.", tiles.size());
		return tiles;
	}
}
